/*
    Copyright © 2013 by Maxim Biro <nurupo.contributions@gmail.com>
    Copyright © 2014-2019 by The qTox Project Contributors

    This file is part of qTox, a Qt-based graphical interface for Tox.

    qTox is libre software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    qTox is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with qTox.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "core.h"
//#include "coreav.h"
#include "corefile.h"

#include "core/coreext.h"
#include "core/dhtserver.h"
#include "core/icoresettings.h"
#include "core/toxlogger.h"
#include "core/toxoptions.h"
#include "core/toxstring.h"
#include "model/status.h"
#include "model/ibootstraplistgenerator.h"
#include "util/strongtype.h"

#include <iostream>
#include <string>
#include <cassert>
#include <chrono>
#include <memory>
#include <random>
#include <shared_mutex>

const std::string Core::TOX_EXT = ".tox";

#define ASSERT_CORE_THREAD assert(std::thread::currentThread() == coreThread.get())

namespace {
/**
 * @brief Parse and log toxcore error enums.
 * @param error Error to handle.
 * @return True if no error, false otherwise.
 */

#define PARSE_ERR(err) parseErr(err, __LINE__)

bool parseErr(Tox_Err_Conference_Title error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_TITLE_OK:
        return true;

    case TOX_ERR_CONFERENCE_TITLE_CONFERENCE_NOT_FOUND:
        std::cout << line << ": Conference title not found" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_TITLE_INVALID_LENGTH:
        std::cout << line << ": Invalid conference title length" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_TITLE_FAIL_SEND:
        std::cout << line << ": Failed to send title packet" << std::endl;
        return false;

    default:
        std::cout << line << ": Unknown Tox_Err_Conference_Title error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_Send_Message error, int line)
{
    switch (error) {
    case TOX_ERR_FRIEND_SEND_MESSAGE_OK:
        return true;

    case TOX_ERR_FRIEND_SEND_MESSAGE_NULL:
        std::cout << line << "Send friend message passed an unexpected null argument" << std::endl;
        return false;

    case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND:
        std::cout << line << "Send friend message could not find friend" << std::endl;
        return false;

    case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED:
        std::cout << line << "Send friend message: friend is offline" << std::endl;
        return false;

    case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ:
        std::cout << line << "Failed to allocate more message queue" << std::endl;
        return false;

    case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG:
        std::cout << line << "Attemped to send message that's too long" << std::endl;
        return false;

    case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY:
        std::cout << line << "Attempted to send an empty message" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown friend send message error:" << static_cast<int>(error) << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Send_Message error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_SEND_MESSAGE_OK:
        return true;

    case TOX_ERR_CONFERENCE_SEND_MESSAGE_CONFERENCE_NOT_FOUND:
        std::cout << line << "Conference not found" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_SEND_MESSAGE_FAIL_SEND:
        std::cout << line << "Conference message failed to send" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_SEND_MESSAGE_NO_CONNECTION:
        std::cout << line << "No connection" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_SEND_MESSAGE_TOO_LONG:
        std::cout << line << "Message too long" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_Send_Message  error:" << static_cast<int>(error) << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Peer_Query error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_PEER_QUERY_OK:
        return true;

    case TOX_ERR_CONFERENCE_PEER_QUERY_CONFERENCE_NOT_FOUND:
        std::cout << line << "Conference not found" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_PEER_QUERY_NO_CONNECTION:
        std::cout << line << "No connection" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_PEER_QUERY_PEER_NOT_FOUND:
        std::cout << line << "Peer not found" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_Peer_Query error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Join error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_JOIN_OK:
        return true;

    case TOX_ERR_CONFERENCE_JOIN_DUPLICATE:
        std::cout << line << "Conference duplicate" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_JOIN_FAIL_SEND:
        std::cout << line << "Conference join failed to send" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_JOIN_FRIEND_NOT_FOUND:
        std::cout << line << "Friend not found" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_JOIN_INIT_FAIL:
        std::cout << line << "Init fail" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_JOIN_INVALID_LENGTH:
        std::cout << line << "Invalid length" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_JOIN_WRONG_TYPE:
        std::cout << line << "Wrong conference type" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_Join error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Get_Type error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_GET_TYPE_OK:
        return true;

    case TOX_ERR_CONFERENCE_GET_TYPE_CONFERENCE_NOT_FOUND:
        std::cout << line << "Conference not found" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_Get_Type error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Invite error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_INVITE_OK:
        return true;

    case TOX_ERR_CONFERENCE_INVITE_CONFERENCE_NOT_FOUND:
        std::cout << line << "Conference not found" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_INVITE_FAIL_SEND:
        std::cout << line << "Conference invite failed to send" << std::endl;
        return false;

    case TOX_ERR_CONFERENCE_INVITE_NO_CONNECTION:
        std::cout << line << "Cannot invite to conference that we're not connected to" << std::endl;
        return false;

    default:
        std::cout << "Unknown Tox_Err_Conference_Invite error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_New error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_NEW_OK:
        return true;

    case TOX_ERR_CONFERENCE_NEW_INIT:
        std::cout << line << "The conference instance failed to initialize" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_New error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_By_Public_Key error, int line)
{
    switch (error) {
    case TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK:
        return true;

    case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL:
        std::cout << line << "null argument when not expected" << std::endl;
        return false;

    case TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND:
        // we use this as a check for friendship, so this can be an expected result
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_By_Public_Key error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Bootstrap error, int line)
{
    switch(error) {
    case TOX_ERR_BOOTSTRAP_OK:
        return true;

    case TOX_ERR_BOOTSTRAP_NULL:
        std::cout << line << "null argument when not expected" << std::endl;
        return false;

    case TOX_ERR_BOOTSTRAP_BAD_HOST:
        std::cout << line << "Could not resolve hostname, or invalid IP address" << std::endl;
        return false;

    case TOX_ERR_BOOTSTRAP_BAD_PORT:
        std::cout << line << "out of range port" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_bootstrap error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_Add error, int line)
{
    switch(error) {
    case TOX_ERR_FRIEND_ADD_OK:
        return true;

    case TOX_ERR_FRIEND_ADD_NULL:
        std::cout << line << "null argument when not expected" << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_TOO_LONG:
        std::cout << line << "The length of the friend request message exceeded" << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_NO_MESSAGE:
        std::cout << line << "The friend request message was empty." << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_OWN_KEY:
        std::cout << line << "The friend address belongs to the sending client." << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_ALREADY_SENT:
        std::cout << line << "The address belongs to a friend that is already on the friend list." << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM:
        std::cout << line << "The friend address checksum failed." << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM:
        std::cout << line << "The address belongs to a friend that is already on the friend list." << std::endl;
        return false;

    case TOX_ERR_FRIEND_ADD_MALLOC:
        std::cout << line << "A memory allocation failed when trying to increase the friend list size." << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_Add error code:" << error << std::endl;
        return false;

    }
}

bool parseErr(Tox_Err_Friend_Delete error, int line)
{
    switch(error) {
    case TOX_ERR_FRIEND_DELETE_OK:
        return true;

    case TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND:
        std::cout << line << "There is no friend with the given friend number" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_Delete error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Set_Info error, int line)
{
    switch (error) {
    case TOX_ERR_SET_INFO_OK:
        return true;

    case TOX_ERR_SET_INFO_NULL:
        std::cout << line << "null argument when not expected" << std::endl;
        return false;

    case TOX_ERR_SET_INFO_TOO_LONG:
        std::cout << line << "Information length exceeded maximum permissible size." << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Set_Info error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_Query error, int line)
{
    switch (error) {
    case TOX_ERR_FRIEND_QUERY_OK:
        return true;

    case TOX_ERR_FRIEND_QUERY_NULL:
        std::cout << line << "null argument when not expected" << std::endl;
        return false;

    case TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND:
        std::cout << line << "The friend_number did not designate a valid friend." << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_Query error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_Get_Public_Key error, int line)
{
    switch (error) {
    case TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK:
        return true;

    case TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND:
        std::cout << line << "There is no friend with the given friend number" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_Get_Public_Key error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Friend_Get_Last_Online error, int line)
{
    switch (error) {
    case TOX_ERR_FRIEND_GET_LAST_ONLINE_OK:
        return true;

    case TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND:
        std::cout << line << "There is no friend with the given friend number" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Friend_Get_Last_Online error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Set_Typing error, int line)
{
    switch (error) {
    case TOX_ERR_SET_TYPING_OK:
        return true;

    case TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND:
        std::cout << line << "There is no friend with the given friend number" << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Set_Typing error code:" << error << std::endl;
        return false;
    }
}

bool parseErr(Tox_Err_Conference_Delete error, int line)
{
    switch (error) {
    case TOX_ERR_CONFERENCE_DELETE_OK:
        return true;

    case TOX_ERR_CONFERENCE_DELETE_CONFERENCE_NOT_FOUND:
        std::cout << line << "The conference number passed did not designate a valid conference." << std::endl;
        return false;

    default:
        std::cout << line << "Unknown Tox_Err_Conference_Delete error code:" << error << std::endl;
        return false;
    }
}

} // namespace

Core::Core(IBootstrapListGenerator& _bootstrapNodes)
    : tox(nullptr)
    , toxTimer()
    , coreLoopLock{}
    , bootstrapNodes(_bootstrapNodes)
{
}

Core::~Core()
{
    /*
     * First stop the thread to stop the timer and avoid Core emitting callbacks
     * into an already destructed CoreAV.
     */
    //toxTimer.stop();
    tox.reset();
}

/**
 * @brief Registers all toxcore callbacks
 * @param tox Tox instance to register the callbacks on
 */
void Core::registerCallbacks(Tox* tox)
{
    tox_callback_friend_request(tox, onFriendRequest);
    tox_callback_friend_message(tox, onFriendMessage);
    tox_callback_friend_name(tox, onFriendNameChange);
    tox_callback_friend_typing(tox, onFriendTypingChange);
    tox_callback_friend_status_message(tox, onStatusMessageChanged);
    tox_callback_friend_status(tox, onUserStatusChanged);
    tox_callback_friend_connection_status(tox, onConnectionStatusChanged);
    tox_callback_friend_read_receipt(tox, onReadReceiptCallback);
//    tox_callback_conference_invite(tox, onGroupInvite);
//    tox_callback_conference_message(tox, onGroupMessage);
//    tox_callback_conference_peer_list_changed(tox, onGroupPeerListChange);
//    tox_callback_conference_peer_name(tox, onGroupPeerNameChange);
//    tox_callback_conference_title(tox, onGroupTitleChange);
    tox_callback_friend_lossless_packet(tox, onLosslessPacket);
}

/**
 * @brief Factory method for the Core object
 * @param savedata empty if new profile or saved data else
 * @param settings Settings specific to Core
 * @return nullptr or a Core object ready to start
 */
ToxCorePtr Core::makeToxCore(const std::vector<unsigned char>& savedata, const ICoreSettings* const settings,
                             IBootstrapListGenerator& bootstrapNodes, ToxCoreErrors* err)
{

    auto toxOptions = ToxOptions::makeToxOptions(savedata, settings);
    if (toxOptions == nullptr) {
        std::cout << "Could not allocate ToxOptions data structure" << std::endl;
        if (err) {
            *err = ToxCoreErrors::ERROR_ALLOC;
        }
        return {};
    }

    ToxCorePtr core(new Core(bootstrapNodes));
    if (core == nullptr) {
        if (err) {
            *err = ToxCoreErrors::ERROR_ALLOC;
        }
        return {};
    }

    Tox_Err_New tox_err;
    core->tox = ToxPtr(tox_new(*toxOptions, &tox_err));

    switch (tox_err) {
    case TOX_ERR_NEW_OK:
        break;

    case TOX_ERR_NEW_LOAD_BAD_FORMAT:
        std::cout << "Failed to parse Tox save data" << std::endl;
        if (err) {
            *err = ToxCoreErrors::BAD_PROXY;
        }
        return {};

    case TOX_ERR_NEW_PORT_ALLOC:
        if (toxOptions->getIPv6Enabled()) {
            toxOptions->setIPv6Enabled(false);
            core->tox = ToxPtr(tox_new(*toxOptions, &tox_err));
            if (tox_err == TOX_ERR_NEW_OK) {
                std::cout << "Core failed to start with IPv6, falling back to IPv4. LAN discovery "
                              "may not work properly." << std::endl;
                break;
            }
        }

        std::cout << "Can't to bind the port" << std::endl;
        if (err) {
            *err = ToxCoreErrors::FAILED_TO_START;
        }
        return {};

    case TOX_ERR_NEW_PROXY_BAD_HOST:
    case TOX_ERR_NEW_PROXY_BAD_PORT:
    case TOX_ERR_NEW_PROXY_BAD_TYPE:
        std::cout << "Bad proxy, error code:" << tox_err << std::endl;
        if (err) {
            *err = ToxCoreErrors::BAD_PROXY;
        }
        return {};

    case TOX_ERR_NEW_PROXY_NOT_FOUND:
        std::cout << "Proxy not found" << std::endl;
        if (err) {
            *err = ToxCoreErrors::BAD_PROXY;
        }
        return {};

    case TOX_ERR_NEW_LOAD_ENCRYPTED:
        std::cout << "Attempted to load encrypted Tox save data" << std::endl;
        if (err) {
            *err = ToxCoreErrors::INVALID_SAVE;
        }
        return {};

    case TOX_ERR_NEW_MALLOC:
        std::cout << "Memory allocation failed" << std::endl;
        if (err) {
            *err = ToxCoreErrors::ERROR_ALLOC;
        }
        return {};

    case TOX_ERR_NEW_NULL:
        std::cout << "A parameter was null" << std::endl;
        if (err) {
            *err = ToxCoreErrors::FAILED_TO_START;
        }
        return {};

    default:
        std::cout << "Toxcore failed to start, unknown error code:" << tox_err << std::endl;
        if (err) {
            *err = ToxCoreErrors::FAILED_TO_START;
        }
        return {};
    }

    // tox should be valid by now
    assert(core->tox != nullptr);

    // create CoreFile
    core->file = CoreFile::makeCoreFile(core.get(), core->tox.get());
    if (!core->file) {
        std::cout << "CoreFile failed to start" << std::endl;
        if (err) {
            *err = ToxCoreErrors::FAILED_TO_START;
        }
        return {};
    }

    core->ext = CoreExt::makeCoreExt(core->tox.get());
    core->friendStatusChanged.connect(&CoreExt::onFriendStatusChanged, core->ext.get());

    registerCallbacks(core->tox.get());


    return core;
}

void Core::onStarted()
{
    // One time initialization stuff
    std::string name = getUsername();
    if (!name.empty()) {
         usernameSet(name);
    }

    std::string msg = getStatusMessage();
    if (!msg.empty()) {
       // emit statusMessageSet(msg);
    }

    ToxId id = getSelfId();
    // Id comes from toxcore, must be valid
    assert(id.isValid());
     idSet(id);

    loadFriends();
    //loadGroups();
    toxTimer.add(std::chrono::microseconds(50), std::bind(&Core::process, this));
   // process(); // starts its own timer
}

/**
 * @brief Starts toxcore and it's event loop, can be called from any thread
 */
void Core::start()
{
    std::thread coreThread(std::bind(&Core::onStarted,this));
    coreThread.detach();
}

const CoreAV* Core::getAv() const
{
    return av;
}

CoreAV* Core::getAv()
{
    return av;
}

void Core::setAv(CoreAV *coreAv)
{
    av = coreAv;
}

CoreFile* Core::getCoreFile() const
{
    return file.get();
}

Tox* Core::getTox() const
{
    return tox.get();
}



const CoreExt* Core::getExt() const
{
    return ext.get();
}

CoreExt* Core::getExt()
{
    return ext.get();
}

/* Using the now commented out statements in checkConnection(), I watched how
 * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials,
 * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks.
 * So I set the tolerance here at 25, and initial DCs should be very rare now.
 * This should be able to go to 50 or 100 without affecting legitimate disconnects'
 * downtime, but lets be conservative for now. Edit: now ~~40~~ 30.
 */
#define CORE_DISCONNECT_TOLERANCE 30

/**
 * @brief Processes toxcore events and ensure we stay connected, called by its own timer
 */
void Core::process()
{
    std::unique_lock<std::recursive_mutex> ml(coreLoopLock);
   // ASSERT_CORE_THREAD;

    static int tolerance = CORE_DISCONNECT_TOLERANCE;
    tox_iterate(tox.get(), this);
    ext->process();

#ifdef DEBUG
    // we want to see the debug messages immediately
    fflush(stdout);
#endif

    // TODO(sudden6): recheck if this is still necessary
    if (checkConnection()) {
        tolerance = CORE_DISCONNECT_TOLERANCE;
    } else if (!(--tolerance)) {
        bootstrapDht();
        tolerance = 3 * CORE_DISCONNECT_TOLERANCE;
    }

    unsigned sleeptime =
        std::min(tox_iteration_interval(tox.get()), getCoreFile()->corefileIterationInterval());

    toxTimer.add(std::chrono::microseconds(sleeptime), std::bind(&Core::process, this));
    //toxTimer.setInterval(sleeptime);//startOnce(sleeptime, std::bind(&Core::process,this));
}

bool Core::checkConnection()
{
    static bool isConnected = false;
    auto selfConnection = tox_self_get_connection_status(tox.get());
    std::string connectionName;
    bool toxConnected = false;
    switch (selfConnection)
    {
        case TOX_CONNECTION_NONE:
            toxConnected = false;
            break;
        case TOX_CONNECTION_TCP:
            toxConnected = true;
            connectionName = "a TCP relay";
            break;
        case TOX_CONNECTION_UDP:
            toxConnected = true;
            connectionName = "the UDP DHT";
            break;
        std::cout << "tox_self_get_connection_status returned unknown enum!"<<std::endl;
    }

    if (toxConnected && !isConnected) {
        std::cout << "Connected to " << connectionName << std::endl;
         connected();
    } else if (!toxConnected && isConnected) {
       std::cout << "Disconnected from the DHT" << std::endl;
         disconnected();
    }

    isConnected = toxConnected;
    return toxConnected;
}

/**
 * @brief Connects us to the Tox network
 */
void Core::bootstrapDht()
{
    std::vector<DhtServer> bootstrapNodesList = bootstrapNodes.getBootstrapnodes();

    int listSize = bootstrapNodesList.size();
    if (!listSize) {
        std::cout << "No bootstrap node list" << std::endl;
        return;
    }

    int i = 0;
    std::mt19937 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<int> distribution(0, listSize - 1);
    static int j = distribution(rng);
    // i think the more we bootstrap, the more we jitter because the more we overwrite nodes
    //while (i < 2) {
        const DhtServer& dhtServer = bootstrapNodesList[j % listSize];
        std::string port = std::to_string(dhtServer.port);
        std::cout<<"Connecting to bootstrap node "<< j % listSize <<" : "<<dhtServer.ipv4<< std::endl;

        const char* address;
        if (dhtServer.ipv4.empty() && !dhtServer.ipv6.empty()) {
            address =   dhtServer.ipv6.c_str();
        } else {
            address = dhtServer.ipv4.c_str();
        }

        // TODO: constucting the pk via ToxId is a workaround
        ToxPk pk = ToxId{dhtServer.userId}.getPublicKey();
        const uint8_t* pkPtr = pk.getData();

        Tox_Err_Bootstrap error;
        if (dhtServer.statusUdp) {
            tox_bootstrap(tox.get(), address, dhtServer.port, pkPtr, &error);
            PARSE_ERR(error);
        }
        if (dhtServer.statusTcp) {
            tox_add_tcp_relay(tox.get(), address, dhtServer.port, pkPtr, &error);
            PARSE_ERR(error);
        }

        // bootstrap off every 5th node (+ a special case to avoid cycles when listSize % 5 == 0)
        j += 5 + !(listSize % 5);
        ++i;
    //}
}

void Core::onFriendRequest(Tox*, const uint8_t* cFriendPk, const uint8_t* cMessage,
                           size_t cMessageSize, void* core)
{
    ToxPk friendPk(cFriendPk);
    std::string requestMessage = ToxString(cMessage, cMessageSize).getstring();
     static_cast<Core*>(core)->friendRequestReceived(friendPk, requestMessage);
}

void Core::onFriendMessage(Tox*, uint32_t friendId, Tox_Message_Type type, const uint8_t* cMessage,
                           size_t cMessageSize, void* core)
{
    bool isAction = (type == TOX_MESSAGE_TYPE_ACTION);
    if(!isAction){
        std::string msg = ToxString(cMessage, cMessageSize).getstring();
         static_cast<Core*>(core)->friendMessageReceived(friendId, msg, isAction);
    }else{
        ToxString msg(cMessage, cMessageSize);
         static_cast<Core*>(core)->friendDataReceived(friendId, msg, isAction);
    }
}

void Core::onFriendNameChange(Tox*, uint32_t friendId, const uint8_t* cName, size_t cNameSize, void* core)
{
    std::string newName = ToxString(cName, cNameSize).getstring();
    // no saveRequest, this callback is called on every connection, not just on name change
     static_cast<Core*>(core)->friendUsernameChanged(friendId, newName);
}

void Core::onFriendTypingChange(Tox*, uint32_t friendId, bool isTyping, void* core)
{
     static_cast<Core*>(core)->friendTypingChanged(friendId, isTyping);
}

void Core::onStatusMessageChanged(Tox*, uint32_t friendId, const uint8_t* cMessage,
                                  size_t cMessageSize, void* core)
{
    std::string message = ToxString(cMessage, cMessageSize).getstring();
    // no saveRequest, this callback is called on every connection, not just on name change
     static_cast<Core*>(core)->friendStatusMessageChanged(friendId, message);
}

void Core::onUserStatusChanged(Tox*, uint32_t friendId, Tox_User_Status userstatus, void* core)
{
    Status::Status status;
    switch (userstatus) {
    case TOX_USER_STATUS_AWAY:
        status = Status::Status::Away;
        break;

    case TOX_USER_STATUS_BUSY:
        status = Status::Status::Busy;
        break;

    default:
        status = Status::Status::Online;
        break;
    }

    // no saveRequest, this callback is called on every connection, not just on name change
     static_cast<Core*>(core)->friendStatusChanged(friendId, status);
}

void Core::onConnectionStatusChanged(Tox*, uint32_t friendId, Tox_Connection status, void* vCore)
{
    Core* core = static_cast<Core*>(vCore);
    Status::Status friendStatus;
    switch (status)
    {
        case TOX_CONNECTION_NONE:
            friendStatus = Status::Status::Offline;
            std::cout << "Disconnected from friend " << friendId << std::endl;
            break;
        case TOX_CONNECTION_TCP:
            friendStatus = Status::Status::Online;
            std::cout << "Connected to friend " << friendId << " through a TCP relay" << std::endl;
            break;
        case TOX_CONNECTION_UDP:
            friendStatus = Status::Status::Online;
            std::cout << "Connected to friend " << friendId << " directly with UDP" << std::endl;
            break;
        std::cout << "tox_callback_friend_connection_status returned unknown enum!" << std::endl;
    }

    // Ignore Online because it will be emited from onUserStatusChanged
    bool isOffline = friendStatus == Status::Status::Offline;
    if (isOffline) {
        core->friendStatusChanged(friendId, friendStatus);
        core->checkLastOnline(friendId);
    }
}



/**
 * @brief Handling of custom lossless packets received by toxcore. Currently only used to forward toxext packets to CoreExt
 */
void Core::onLosslessPacket(Tox*, uint32_t friendId,
                            const uint8_t* data, size_t length, void* vCore)
{
    Core* core = static_cast<Core*>(vCore);
    core->ext->onLosslessPacket(friendId, data, length);
}

void Core::onReadReceiptCallback(Tox*, uint32_t friendId, uint32_t receipt, void* core)
{
     static_cast<Core*>(core)->receiptRecieved(friendId, ReceiptNum{receipt});
}

void Core::acceptFriendRequest(const ToxPk& friendPk)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    Tox_Err_Friend_Add error;
    uint32_t friendId = tox_friend_add_norequest(tox.get(), friendPk.getData(), &error);
    if (PARSE_ERR(error)) {
         saveRequest();
         friendAdded(friendId, friendPk);
    } else {
         failedToAddFriend(friendPk,"");
    }
}

/**
 * @brief Checks that sending friendship request is correct and returns error message accordingly
 * @param friendId Id of a friend which request is destined to
 * @param message Friendship request message
 * @return Returns empty string if sending request is correct, according error message otherwise
 */
std::string Core::getFriendRequestErrorMessage(const ToxId& friendId, const std::string& message) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    if (!friendId.isValid()) {
        return std::string("Invalid Tox ID", "Error while sending friend request");
    }

    if (message.empty()) {
        return std::string("You need to write a message with your request",
                  "Error while sending friend request");
    }

    if (message.length() > static_cast<int>(tox_max_friend_request_length())) {
        return std::string("Your message is too long!", "Error while sending friend request");
    }

    if (hasFriendWithPublicKey(friendId.getPublicKey())) {
        return std::string("Friend is already added", "Error while sending friend request");
    }

    return std::string{};
}

void Core::requestFriendship(const ToxId& friendId, const std::string& message)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    ToxPk friendPk = friendId.getPublicKey();
    std::string errorMessage = getFriendRequestErrorMessage(friendId, message);
    if (!errorMessage.empty()) {
         failedToAddFriend(friendPk, errorMessage);
         saveRequest();
        return;
    }

    ToxString cMessage(message);
    Tox_Err_Friend_Add error;
    uint32_t friendNumber =
        tox_friend_add(tox.get(), friendId.getBytes(), cMessage.data(), cMessage.size(), &error);
    if (PARSE_ERR(error)) {
        std::cout << "Requested friendship from " << friendNumber << std::endl;
         saveRequest();
         friendAdded(friendNumber, friendPk);
         requestSent(friendPk, message);
    } else {
        std::cout << "Failed to send friend request" << std::endl;
         failedToAddFriend(friendPk,"");
    }
}


bool Core::sendDataWithType(uint32_t friendId, const ToxString& data, Tox_Message_Type type, ReceiptNum& receipt)
{
    int size = data.size();
    auto maxSize = static_cast<int>(getMaxMessageSize());
    if (size > maxSize) {
        assert(false);
        std::cout << "Core::sendMessageWithType called with message of size:" << size
                    << "when max is:" << maxSize << ". Ignoring." << std::endl;
        return false;
    }

    Tox_Err_Friend_Send_Message error;
    receipt = ReceiptNum{tox_friend_send_message(tox.get(), friendId, type, data.data(),
                                                 data.size(), &error)};
    if (PARSE_ERR(error)) {
        return true;
    }
    return false;
}
bool Core::sendMessageWithType(uint32_t friendId, const std::string& message, Tox_Message_Type type,
                               ReceiptNum& receipt)
{
    int size = message.size();
    auto maxSize = static_cast<int>(getMaxMessageSize());
    if (size > maxSize) {
        assert(false);
        std::cout << "Core::sendMessageWithType called with message of size:" << size
                    << "when max is:" << maxSize << ". Ignoring." << std::endl;
        return false;
    }

    ToxString cMessage(message);
    Tox_Err_Friend_Send_Message error;
    receipt = ReceiptNum{tox_friend_send_message(tox.get(), friendId, type, cMessage.data(),
                                                 cMessage.size(), &error)};
    if (PARSE_ERR(error)) {
        return true;
    }
    return false;
}

bool Core::sendMessage(uint32_t friendId, const std::string& message, ReceiptNum& receipt)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    return sendMessageWithType(friendId, message, TOX_MESSAGE_TYPE_NORMAL, receipt);
}

bool Core::sendAction(uint32_t friendId, const std::string& action, ReceiptNum& receipt)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    return sendMessageWithType(friendId, action, TOX_MESSAGE_TYPE_ACTION, receipt);
}
bool Core::sendActionData(uint32_t friendId, const ToxString& actiondata, ReceiptNum& receipt)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    return sendDataWithType(friendId, actiondata, TOX_MESSAGE_TYPE_ACTION, receipt);
}

void Core::sendTyping(uint32_t friendId, bool typing)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Set_Typing error;
    tox_self_set_typing(tox.get(), friendId, typing, &error);
    if (!PARSE_ERR(error)) {
         failedToSetTyping(typing);
    }
}

void Core::sendGroupMessageWithType(int groupId, const std::string& message, Tox_Message_Type type)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    int size = message.size();
    auto maxSize = static_cast<int>(getMaxMessageSize());
    if (size > maxSize) {
        std::cout << "Core::sendMessageWithType called with message of size:" << size
                    << "when max is:" << maxSize << ". Ignoring." << std::endl;
        return;
    }

    ToxString cMsg(message);
    Tox_Err_Conference_Send_Message error;
    tox_conference_send_message(tox.get(), groupId, type, cMsg.data(), cMsg.size(), &error);
    if (!PARSE_ERR(error)) {
         //groupSentFailed(groupId);
        return;
    }
}

//void Core::sendGroupMessage(int groupId, const std::string& message)
//{
//    QMutexLocker ml{&coreLoopLock};

//    sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_NORMAL);
//}

//void Core::sendGroupAction(int groupId, const std::string& message)
//{
//    QMutexLocker ml{&coreLoopLock};

//    sendGroupMessageWithType(groupId, message, TOX_MESSAGE_TYPE_ACTION);
//}

//void Core::changeGroupTitle(int groupId, const std::string& title)
//{
//    QMutexLocker ml{&coreLoopLock};

//    ToxString cTitle(title);
//    Tox_Err_Conference_Title error;
//    tox_conference_set_title(tox.get(), groupId, cTitle.data(), cTitle.size(), &error);
//    if (PARSE_ERR(error)) {
//        emit saveRequest();
//        emit groupTitleChanged(groupId, getUsername(), title);
//    }
//}

void Core::removeFriend(uint32_t friendId)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Friend_Delete error;
    tox_friend_delete(tox.get(), friendId, &error);
    if (!PARSE_ERR(error)) {
         failedToRemoveFriend(friendId);
        return;
    }

     saveRequest();
     friendRemoved(friendId);
}

//void Core::removeGroup(int groupId)
//{
//    QMutexLocker ml{&coreLoopLock};

//    Tox_Err_Conference_Delete error;
//    tox_conference_delete(tox.get(), groupId, &error);
//    if (PARSE_ERR(error)) {
//        emit saveRequest();

//        /*
//         * TODO(sudden6): this is probably not (thread-)safe, but can be ignored for now since
//         * we don't change av at runtime.
//         */

////        if (av) {
////            av->leaveGroupCall(groupId);
////        }
//    }
//}

/**
 * @brief Returns our username, or an empty string on failure
 */
std::string Core::getUsername() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    std::string sname;
    if (!tox) {
        return sname;
    }

    int size = tox_self_get_name_size(tox.get());
    if (!size) {
        return {};
    }
    std::vector<uint8_t> nameBuf(size);
    tox_self_get_name(tox.get(), nameBuf.data());
    return ToxString(nameBuf.data(), size).getstring();
}

void Core::setUsername(const std::string& username)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    if (username == getUsername()) {
        return;
    }

    ToxString cUsername(username);
    Tox_Err_Set_Info error;
    tox_self_set_name(tox.get(), cUsername.data(), cUsername.size(), &error);
    if (!PARSE_ERR(error)) {
       // emit failedToSetUsername(username);
        return;
    }

     usernameSet(username);
     saveRequest();
}

/**
 * @brief Returns our Tox ID
 */
ToxId Core::getSelfId() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00};
    tox_self_get_address(tox.get(), friendId);
    return ToxId(friendId, TOX_ADDRESS_SIZE);
}

/**
 * @brief Gets self public key
 * @return Self PK
 */
ToxPk Core::getSelfPublicKey() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    uint8_t friendId[TOX_ADDRESS_SIZE] = {0x00};
    tox_self_get_address(tox.get(), friendId);
    return ToxPk(friendId);
}

/**
 * @brief Returns our public and private keys
 */
std::pair<std::vector<unsigned char>, std::vector<unsigned char>> Core::getKeypair() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    std::pair<std::vector<unsigned char>, std::vector<unsigned char>> keypair;
    assert(tox != nullptr);

    std::vector<unsigned char> pk(TOX_PUBLIC_KEY_SIZE, 0x00);
    std::vector<unsigned char> sk(TOX_SECRET_KEY_SIZE, 0x00);
    tox_self_get_public_key(tox.get(), reinterpret_cast<uint8_t*>(pk.data()));
    tox_self_get_secret_key(tox.get(), reinterpret_cast<uint8_t*>(sk.data()));
    keypair.first = pk;
    keypair.second = sk;
    return keypair;
}

/**
 * @brief Returns our status message, or an empty string on failure
 */
std::string Core::getStatusMessage() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    assert(tox != nullptr);

    size_t size = tox_self_get_status_message_size(tox.get());
    if (!size) {
        return {};
    }
    std::vector<uint8_t> nameBuf(size);
    tox_self_get_status_message(tox.get(), nameBuf.data());
    return ToxString(nameBuf.data(), size).getstring();
}

/**
 * @brief Returns our user status
 */
Status::Status Core::getStatus() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    return static_cast<Status::Status>(tox_self_get_status(tox.get()));
}

void Core::setStatusMessage(const std::string& message)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    if (message == getStatusMessage()) {
        return;
    }

    ToxString cMessage(message);
    Tox_Err_Set_Info error;
    tox_self_set_status_message(tox.get(), cMessage.data(), cMessage.size(), &error);
    if (!PARSE_ERR(error)) {
         failedToSetStatusMessage(message);
        return;
    }

     saveRequest();
     statusMessageSet(message);
}

void Core::setStatus(Status::Status status)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_User_Status userstatus;
    switch (status) {
    case Status::Status::Online:
        userstatus = TOX_USER_STATUS_NONE;
        break;

    case Status::Status::Away:
        userstatus = TOX_USER_STATUS_AWAY;
        break;

    case Status::Status::Busy:
        userstatus = TOX_USER_STATUS_BUSY;
        break;

    default:
        return;
        break;
    }

    tox_self_set_status(tox.get(), userstatus);
     saveRequest();
     statusSet(status);
}

/**
 * @brief Returns the unencrypted tox save data
 */
std::vector<unsigned char> Core::getToxSaveData()
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    uint32_t fileSize = tox_get_savedata_size(tox.get());
    std::vector<unsigned char> data;
    data.resize(fileSize);
    tox_get_savedata(tox.get(), reinterpret_cast<uint8_t*>(data.data()));
    return data;
}

void Core::loadFriends()
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    const size_t friendCount = tox_self_get_friend_list_size(tox.get());
    if (friendCount == 0) {
        return;
    }

    std::vector<uint32_t> ids(friendCount);
    tox_self_get_friend_list(tox.get(), ids.data());
    uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00};
    for (size_t i = 0; i < friendCount; ++i) {
        Tox_Err_Friend_Get_Public_Key keyError;
        tox_friend_get_public_key(tox.get(), ids[i], friendPk, &keyError);
        if (!PARSE_ERR(keyError)) {
            continue;
        }
         friendAdded(ids[i], ToxPk(friendPk));
         friendUsernameChanged(ids[i], getFriendUsername(ids[i]));
        Tox_Err_Friend_Query queryError;
        size_t statusMessageSize = tox_friend_get_status_message_size(tox.get(), ids[i], &queryError);
        if (PARSE_ERR(queryError) && statusMessageSize) {
            std::vector<uint8_t> messageData(statusMessageSize);
            tox_friend_get_status_message(tox.get(), ids[i], messageData.data(), &queryError);
            std::string friendStatusMessage = ToxString(messageData.data(), statusMessageSize).getstring();
             friendStatusMessageChanged(ids[i], friendStatusMessage);
        }
        checkLastOnline(ids[i]);
    }
}

//void Core::loadGroups()
//{
//    QMutexLocker ml{&coreLoopLock};

//    const size_t groupCount = tox_conference_get_chatlist_size(tox.get());
//    if (groupCount == 0) {
//        return;
//    }

//    std::vector<uint32_t> groupNumbers(groupCount);
//    tox_conference_get_chatlist(tox.get(), groupNumbers.data());

//    for (size_t i = 0; i < groupCount; ++i) {
//        Tox_Err_Conference_Title error;
//        std::string name;
//        const auto groupNumber = groupNumbers[i];
//        size_t titleSize = tox_conference_get_title_size(tox.get(), groupNumber, &error);
//        const GroupId persistentId = getGroupPersistentId(groupNumber);
//        const std::string defaultName = tr("Groupchat %1").arg(persistentId.toString().left(8));
//        if (PARSE_ERR(error) || !titleSize) {
//            std::vector<uint8_t> nameBuf(titleSize);
//            tox_conference_get_title(tox.get(), groupNumber, nameBuf.data(), &error);
//            if (PARSE_ERR(error)) {
//                name = ToxString(nameBuf.data(), titleSize).getstring();
//            } else {
//                name = defaultName;
//            }
//        } else {
//            name = defaultName;
//        }
////        if (getGroupAvEnabled(groupNumber)) {
////            if (toxav_groupchat_enable_av(tox.get(), groupNumber, CoreAV::groupCallCallback, this)) {
////                std::cout << "Failed to enable audio on loaded group" << groupNumber;
////            }
////        }
//        emit emptyGroupCreated(groupNumber, persistentId, name);
//    }
//}

void Core::checkLastOnline(uint32_t friendId)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Friend_Get_Last_Online error;
    const uint64_t lastOnline = tox_friend_get_last_online(tox.get(), friendId, &error);
    if (PARSE_ERR(error)) {
         //friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline));
    }
}

/**
 * @brief Returns the list of friendIds in our friendlist, an empty list on error
 */
std::vector<uint32_t> Core::getFriendList() const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    std::vector<uint32_t> friends;
    friends.resize(tox_self_get_friend_list_size(tox.get()));
    tox_self_get_friend_list(tox.get(), friends.data());
    return friends;
}

//GroupId Core::getGroupPersistentId(uint32_t groupNumber) const
//{
//    QMutexLocker ml{&coreLoopLock};

//    std::vector<uint8_t> idBuff(TOX_CONFERENCE_UID_SIZE);
//    if (tox_conference_get_id(tox.get(), groupNumber,
//                              idBuff.data())) {
//        return GroupId{idBuff.data()};
//    } else {
//        std::cout << "Failed to get conference ID of group" << groupNumber;
//        return {};
//    }
//}

/**
 * @brief Get number of peers in the conference.
 * @return The number of peers in the conference. UINT32_MAX on failure.
 */
//uint32_t Core::getGroupNumberPeers(int groupId) const
//{
//    QMutexLocker ml{&coreLoopLock};

//    Tox_Err_Conference_Peer_Query error;
//    uint32_t count = tox_conference_peer_count(tox.get(), groupId, &error);
//    if (!PARSE_ERR(error)) {
//        return std::numeric_limits<uint32_t>::max();
//    }

//    return count;
//}

/**
 * @brief Get the name of a peer of a group
 */
//std::string Core::getGroupPeerName(int groupId, int peerId) const
//{
//    QMutexLocker ml{&coreLoopLock};

//    Tox_Err_Conference_Peer_Query error;
//    size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, peerId, &error);
//    if (!PARSE_ERR(error) || !length) {
//        return std::string{};
//    }

//    std::vector<uint8_t> nameBuf(length);
//    tox_conference_peer_get_name(tox.get(), groupId, peerId, nameBuf.data(), &error);
//    if (!PARSE_ERR(error)) {
//        return std::string{};
//    }

//    return ToxString(nameBuf.data(), length).getstring();
//}

/**
 * @brief Get the public key of a peer of a group
 */
//ToxPk Core::getGroupPeerPk(int groupId, int peerId) const
//{
//    QMutexLocker ml{&coreLoopLock};

//    uint8_t friendPk[TOX_PUBLIC_KEY_SIZE] = {0x00};
//    Tox_Err_Conference_Peer_Query error;
//    tox_conference_peer_get_public_key(tox.get(), groupId, peerId, friendPk, &error);
//    if (!PARSE_ERR(error)) {
//        return ToxPk{};
//    }

//    return ToxPk(friendPk);
//}

/**
 * @brief Get the names of the peers of a group
 */
//std::stringList Core::getGroupPeerNames(int groupId) const
//{
//    QMutexLocker ml{&coreLoopLock};

//    assert(tox != nullptr);

//    uint32_t nPeers = getGroupNumberPeers(groupId);
//    if (nPeers == std::numeric_limits<uint32_t>::max()) {
//        std::cout << "getGroupPeerNames: Unable to get number of peers";
//        return {};
//    }

//    std::stringList names;
//    for (int i = 0; i < static_cast<int>(nPeers); ++i) {
//        Tox_Err_Conference_Peer_Query error;
//        size_t length = tox_conference_peer_get_name_size(tox.get(), groupId, i, &error);

//        if (!PARSE_ERR(error) || !length) {
//            names.append(std::string());
//            continue;
//        }

//        std::vector<uint8_t> nameBuf(length);
//        tox_conference_peer_get_name(tox.get(), groupId, i, nameBuf.data(), &error);
//        if (PARSE_ERR(error)) {
//            names.append(ToxString(nameBuf.data(), length).getstring());
//        } else {
//            names.append(std::string());
//        }
//    }

//    assert(names.size() == static_cast<int>(nPeers));

//    return names;
//}

/**
 * @brief Check, that group has audio or video stream
 * @param groupId Id of group to check
 * @return True for AV groups, false for text-only groups
 */
//bool Core::getGroupAvEnabled(int groupId) const
//{
//    QMutexLocker ml{&coreLoopLock};
//    Tox_Err_Conference_Get_Type error;
//    Tox_Conference_Type type = tox_conference_get_type(tox.get(), groupId, &error);
//    PARSE_ERR(error);
//    // would be nice to indicate to caller that we don't actually know..
//    return type == TOX_CONFERENCE_TYPE_AV;
//}

/**
 * @brief Accept a groupchat invite.
 * @param inviteInfo Object which contains info about group invitation
 *
 * @return Conference number on success, UINT32_MAX on failure.
 */
//uint32_t Core::joinGroupchat(const GroupInvite& inviteInfo)
//{
//    QMutexLocker ml{&coreLoopLock};

//    const uint32_t friendId = inviteInfo.getFriendId();
//    const uint8_t confType = inviteInfo.getType();
//    const std::vector<unsigned char> invite = inviteInfo.getInvite();
//    const uint8_t* const cookie = reinterpret_cast<const uint8_t*>(invite.data());
//    const size_t cookieLength = invite.length();
//    uint32_t groupNum{std::numeric_limits<uint32_t>::max()};
//    switch (confType) {
//    case TOX_CONFERENCE_TYPE_TEXT: {
//        std::cout << std::string("Trying to accept invite for text group chat sent by friend %1").arg(friendId);
//        Tox_Err_Conference_Join error;
//        groupNum = tox_conference_join(tox.get(), friendId, cookie, cookieLength, &error);
//        if (!PARSE_ERR(error)) {
//            groupNum = std::numeric_limits<uint32_t>::max();
//        }
//        break;
//    }
////    case TOX_CONFERENCE_TYPE_AV: {
////        std::cout << std::string("Trying to join AV groupchat invite sent by friend %1").arg(friendId);
////        groupNum = toxav_join_av_groupchat(tox.get(), friendId, cookie, cookieLength,
////                                           CoreAV::groupCallCallback, const_cast<Core*>(this));
////        break;
////    }
//    default:
//        std::cout << "joinGroupchat: Unknown groupchat type " << confType;
//    }
//    if (groupNum != std::numeric_limits<uint32_t>::max()) {
//        emit saveRequest();
//        emit groupJoined(groupNum, getGroupPersistentId(groupNum));
//    }
//    return groupNum;
//}

//void Core::groupInviteFriend(uint32_t friendId, int groupId)
//{
//    QMutexLocker ml{&coreLoopLock};

//    Tox_Err_Conference_Invite error;
//    tox_conference_invite(tox.get(), friendId, groupId, &error);
//    PARSE_ERR(error);
//}

//int Core::createGroup(uint8_t type)
//{
//    QMutexLocker ml{&coreLoopLock};

//    if (type == TOX_CONFERENCE_TYPE_TEXT) {
//        Tox_Err_Conference_New error;
//        uint32_t groupId = tox_conference_new(tox.get(), &error);
//        if (PARSE_ERR(error)) {
//            emit saveRequest();
//            emit emptyGroupCreated(groupId, getGroupPersistentId(groupId));
//            return groupId;
//        } else {
//            return std::numeric_limits<uint32_t>::max();
//        }
//    } else if (type == TOX_CONFERENCE_TYPE_AV) {
//        // unlike tox_conference_new, toxav_add_av_groupchat does not have an error enum, so -1 group number is our
//        // only indication of an error
////        int groupId = toxav_add_av_groupchat(tox.get(), CoreAV::groupCallCallback, this);
////        if (groupId != -1) {
////            emit saveRequest();
////            emit emptyGroupCreated(groupId, getGroupPersistentId(groupId));
////        } else {
////            std::cout << "Unknown error creating AV groupchat";
////        }
////        return groupId;
//    } else {
//        std::cout << "createGroup: Unknown type " << type;
//        return -1;
//    }
//}

/**
 * @brief Checks if a friend is online. Unknown friends are considered offline.
 */
bool Core::isFriendOnline(uint32_t friendId) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Friend_Query error;
    Tox_Connection connection = tox_friend_get_connection_status(tox.get(), friendId, &error);
    PARSE_ERR(error);
    return connection != TOX_CONNECTION_NONE;
}

/**
 * @brief Checks if we have a friend by public key
 */
bool Core::hasFriendWithPublicKey(const ToxPk& publicKey) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    if (publicKey.isEmpty()) {
        return false;
    }

    Tox_Err_Friend_By_Public_Key error;
    (void)tox_friend_by_public_key(tox.get(), publicKey.getData(), &error);
    return PARSE_ERR(error);
}

/**
 * @brief Get the public key part of the ToxID only
 */
ToxPk Core::getFriendPublicKey(uint32_t friendNumber) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    uint8_t rawid[TOX_PUBLIC_KEY_SIZE];
    Tox_Err_Friend_Get_Public_Key error;
    tox_friend_get_public_key(tox.get(), friendNumber, rawid, &error);
    if (!PARSE_ERR(error)) {
        std::cout << "getFriendPublicKey: Getting public key failed" << std::endl;
        return ToxPk();
    }

    return ToxPk(rawid);
}

/**
 * @brief Get the username of a friend
 */
std::string Core::getFriendUsername(uint32_t friendnumber) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Friend_Query error;
    size_t nameSize = tox_friend_get_name_size(tox.get(), friendnumber, &error);
    if (!PARSE_ERR(error) || !nameSize) {
        return std::string();
    }

    std::vector<uint8_t> nameBuf(nameSize);
    tox_friend_get_name(tox.get(), friendnumber, nameBuf.data(), &error);
    if (!PARSE_ERR(error)) {
        return std::string();
    }
    return ToxString(nameBuf.data(), nameSize).getstring();
}

uint64_t Core::getMaxMessageSize() const
{
    /*
     * TODO: Remove this hack; the reported max message length we receive from c-toxcore
     * as of 08-02-2019 is inaccurate, causing us to generate too large messages when splitting
     * them up.
     *
     * The inconsistency lies in c-toxcore group.c:2480 using MAX_GROUP_MESSAGE_DATA_LEN to verify
     * message size is within limit, but tox_max_message_length giving a different size limit to us.
     *
     * (uint32_t tox_max_message_length(void); declared in tox.h, unable to see explicit definition)
     */
    return tox_max_message_length() - 50;
}

std::string Core::getPeerName(const ToxPk& id) const
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };

    Tox_Err_Friend_By_Public_Key keyError;
    uint32_t friendId = tox_friend_by_public_key(tox.get(), id.getData(), &keyError);
    if (!PARSE_ERR(keyError)) {
        std::cout << "getPeerName: No such peer" << std::endl;
        return {};
    }

    Tox_Err_Friend_Query queryError;
    const size_t nameSize = tox_friend_get_name_size(tox.get(), friendId, &queryError);
    if (!PARSE_ERR(queryError) || !nameSize) {
        return {};
    }

    std::vector<uint8_t> nameBuf(nameSize);
    tox_friend_get_name(tox.get(), friendId, nameBuf.data(), &queryError);
    if (!PARSE_ERR(queryError)) {
        std::cout << "getPeerName: Can't get name of friend "<<friendId << std::endl;
        return {};
    }

    return ToxString(nameBuf.data(), nameSize).getstring();
}

/**
 * @brief Sets the NoSpam value to prevent friend request spam
 * @param nospam an arbitrary which becomes part of the Tox ID
 */
void Core::setNospam(uint32_t nospam)
{
    std::unique_lock<std::recursive_mutex> ml{ coreLoopLock };
    tox_self_set_nospam(tox.get(), nospam);
     idSet(getSelfId());
}
